Completed
Branch v9.1.x (4df20f)
by Rafael S.
08:09
created

WaveFileParser.getCueBytes_   A

Complexity

Conditions 2

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 14
rs 9.9
c 0
b 0
f 0
cc 2
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFileParser class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import { WaveFileReader } from './wavefile-reader';
31
import writeString from './write-string';
32
import { packTo, packStringTo, packString, pack } from 'byte-data';
33
34
/**
35
 * A class to read and write wav files.
36
 * @extends WaveFileReader
37
 */
38
export class WaveFileParser extends WaveFileReader {
39
40
  /**
41
   * Return a byte buffer representig the WaveFileParser object as a .wav file.
42
   * The return value of this method can be written straight to disk.
43
   * @return {!Uint8Array} A wav file.
44
   */
45
  toBuffer() {
46
    this.uInt16.be = this.container === 'RIFX';
47
    this.uInt32.be = this.uInt16.be;
48
    /** @type {!Array<!Array<number>>} */
49
    let fileBody = [
50
      this.getJunkBytes_(),
51
      this.getDs64Bytes_(),
52
      this.getBextBytes_(),
53
      this.getiXMLBytes_(),
54
      this.getFmtBytes_(),
55
      this.getFactBytes_(),
56
      packString(this.data.chunkId),
57
      pack(this.data.samples.length, this.uInt32),
58
      this.data.samples,
59
      this.getCueBytes_(),
60
      this.getSmplBytes_(),
61
      this.getLISTBytes_(),
62
      this.get_PMXBytes_()
63
    ];
64
    /** @type {number} */
65
    let fileBodyLength = 0;
66
    for (let i=0; i<fileBody.length; i++) {
67
      fileBodyLength += fileBody[i].length;
68
    }
69
    /** @type {!Uint8Array} */
70
    let file = new Uint8Array(fileBodyLength + 12);
71
    /** @type {number} */
72
    let index = 0;
73
    index = packStringTo(this.container, file, index);
74
    index = packTo(fileBodyLength + 4, this.uInt32, file, index);
75
    index = packStringTo(this.format, file, index);
76
    for (let i=0; i<fileBody.length; i++) {
77
      file.set(fileBody[i], index);
78
      index += fileBody[i].length;
79
    }
80
    return file;
81
  }
82
83
  /**
84
   * Return the bytes of the 'bext' chunk.
85
   * @private
86
   */
87
  getBextBytes_() {
88
    /** @type {!Array<number>} */
89
    let bytes = [];
90
    this.enforceBext_();
91
    if (this.bext.chunkId) {
92
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
93
      bytes = bytes.concat(
94
        packString(this.bext.chunkId),
95
        pack(602 + this.bext.codingHistory.length, this.uInt32),
96
        writeString(this.bext.description, 256),
97
        writeString(this.bext.originator, 32),
98
        writeString(this.bext.originatorReference, 32),
99
        writeString(this.bext.originationDate, 10),
100
        writeString(this.bext.originationTime, 8),
101
        pack(this.bext.timeReference[0], this.uInt32),
102
        pack(this.bext.timeReference[1], this.uInt32),
103
        pack(this.bext.version, this.uInt16),
104
        writeString(this.bext.UMID, 64),
105
        pack(this.bext.loudnessValue, this.uInt16),
106
        pack(this.bext.loudnessRange, this.uInt16),
107
        pack(this.bext.maxTruePeakLevel, this.uInt16),
108
        pack(this.bext.maxMomentaryLoudness, this.uInt16),
109
        pack(this.bext.maxShortTermLoudness, this.uInt16),
110
        writeString(this.bext.reserved, 180),
111
        writeString(
112
          this.bext.codingHistory, this.bext.codingHistory.length));
113
    }
114
    this.assureByteLen_(bytes);
115
    return bytes;
116
  }
117
118
  /**
119
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
120
   * @private
121
   */
122
  enforceBext_() {
123
    for (let prop in this.bext) {
124
      if (this.bext.hasOwnProperty(prop)) {
125
        if (this.bext[prop] && prop != 'timeReference') {
126
          this.bext.chunkId = 'bext';
127
          break;
128
        }
129
      }
130
    }
131
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
132
      this.bext.chunkId = 'bext';
133
    }
134
  }
135
136
  /**
137
   * Push a null byte into a byte array if
138
   * the byte count is odd.
139
   * @param {!Array<number>} bytes The byte array.
140
   * @private
141
   */
142
  assureByteLen_(bytes) {
143
    if (bytes.length % 2) {
144
      bytes.push(0);
145
    }
146
  }
147
148
  /**
149
   * Return the bytes of the 'iXML' chunk.
150
   * @return {!Array<number>} The 'iXML' chunk bytes.
151
   * @private
152
   */
153
  getiXMLBytes_() {
154
    /** @type {!Array<number>} */
155
    let bytes = [];
156
    if (this.iXML.chunkId) {
157
      bytes = bytes.concat(
158
        packString(this.iXML.chunkId),
159
        pack(this.iXML.chunkSize, this.uInt32),
160
        packString(this.iXML.value));
161
    }
162
    this.assureByteLen_(bytes);
163
    return bytes;
164
  }
165
166
  /**
167
   * Return the bytes of the 'ds64' chunk.
168
   * @return {!Array<number>} The 'ds64' chunk bytes.
169
   * @private
170
   */
171
  getDs64Bytes_() {
172
    /** @type {!Array<number>} */
173
    let bytes = [];
174
    if (this.ds64.chunkId) {
175
      bytes = bytes.concat(
176
        packString(this.ds64.chunkId),
177
        pack(this.ds64.chunkSize, this.uInt32),
178
        pack(this.ds64.riffSizeHigh, this.uInt32),
179
        pack(this.ds64.riffSizeLow, this.uInt32),
180
        pack(this.ds64.dataSizeHigh, this.uInt32),
181
        pack(this.ds64.dataSizeLow, this.uInt32),
182
        pack(this.ds64.originationTime, this.uInt32),
183
        pack(this.ds64.sampleCountHigh, this.uInt32),
184
        pack(this.ds64.sampleCountLow, this.uInt32));
185
    }
186
    //if (this.ds64.tableLength) {
187
    //  ds64Bytes = ds64Bytes.concat(
188
    //    pack(this.ds64.tableLength, this.uInt32),
189
    //    this.ds64.table);
190
    //}
191
    return bytes;
192
  }
193
194
  /**
195
   * Return the bytes of the 'cue ' chunk.
196
   * @return {!Array<number>} The 'cue ' chunk bytes.
197
   * @private
198
   */
199
  getCueBytes_() {
200
    /** @type {!Array<number>} */
201
    let bytes = [];
202
    if (this.cue.chunkId) {
203
      /** @type {!Array<number>} */
204
      let cuePointsBytes = this.getCuePointsBytes_();
205
      bytes = bytes.concat(
206
        packString(this.cue.chunkId),
207
        pack(cuePointsBytes.length + 4, this.uInt32),
208
        pack(this.cue.dwCuePoints, this.uInt32),
209
        cuePointsBytes);
210
    }
211
    return bytes;
212
  }
213
214
  /**
215
   * Return the bytes of the 'cue ' points.
216
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
217
   * @private
218
   */
219
  getCuePointsBytes_() {
220
    /** @type {!Array<number>} */
221
    let points = [];
222
    for (let i=0; i<this.cue.dwCuePoints; i++) {
223
      points = points.concat(
224
        pack(this.cue.points[i].dwName, this.uInt32),
225
        pack(this.cue.points[i].dwPosition, this.uInt32),
226
        packString(this.cue.points[i].fccChunk),
227
        pack(this.cue.points[i].dwChunkStart, this.uInt32),
228
        pack(this.cue.points[i].dwBlockStart, this.uInt32),
229
        pack(this.cue.points[i].dwSampleOffset, this.uInt32));
230
    }
231
    return points;
232
  }
233
234
  /**
235
   * Return the bytes of the 'smpl' chunk.
236
   * @return {!Array<number>} The 'smpl' chunk bytes.
237
   * @private
238
   */
239
  getSmplBytes_() {
240
    /** @type {!Array<number>} */
241
    let bytes = [];
242
    if (this.smpl.chunkId) {
243
      /** @type {!Array<number>} */
244
      let smplLoopsBytes = this.getSmplLoopsBytes_();
245
      bytes = bytes.concat(
246
        packString(this.smpl.chunkId),
247
        pack(smplLoopsBytes.length + 36, this.uInt32),
248
        pack(this.smpl.dwManufacturer, this.uInt32),
249
        pack(this.smpl.dwProduct, this.uInt32),
250
        pack(this.smpl.dwSamplePeriod, this.uInt32),
251
        pack(this.smpl.dwMIDIUnityNote, this.uInt32),
252
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32),
253
        pack(this.smpl.dwSMPTEFormat, this.uInt32),
254
        pack(this.smpl.dwSMPTEOffset, this.uInt32),
255
        pack(this.smpl.dwNumSampleLoops, this.uInt32),
256
        pack(this.smpl.dwSamplerData, this.uInt32),
257
        smplLoopsBytes);
258
    }
259
    return bytes;
260
  }
261
262
  /**
263
   * Return the bytes of the 'smpl' loops.
264
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
265
   * @private
266
   */
267
  getSmplLoopsBytes_() {
268
    /** @type {!Array<number>} */
269
    let loops = [];
270
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
271
      loops = loops.concat(
272
        pack(this.smpl.loops[i].dwName, this.uInt32),
273
        pack(this.smpl.loops[i].dwType, this.uInt32),
274
        pack(this.smpl.loops[i].dwStart, this.uInt32),
275
        pack(this.smpl.loops[i].dwEnd, this.uInt32),
276
        pack(this.smpl.loops[i].dwFraction, this.uInt32),
277
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32));
278
    }
279
    return loops;
280
  }
281
282
  /**
283
   * Return the bytes of the 'fact' chunk.
284
   * @return {!Array<number>} The 'fact' chunk bytes.
285
   * @private
286
   */
287
  getFactBytes_() {
288
    /** @type {!Array<number>} */
289
    let bytes = [];
290
    if (this.fact.chunkId) {
291
      bytes = bytes.concat(
292
        packString(this.fact.chunkId),
293
        pack(this.fact.chunkSize, this.uInt32),
294
        pack(this.fact.dwSampleLength, this.uInt32));
295
    }
296
    return bytes;
297
  }
298
299
  /**
300
   * Return the bytes of the 'fmt ' chunk.
301
   * @return {!Array<number>} The 'fmt' chunk bytes.
302
   * @throws {Error} if no 'fmt ' chunk is present.
303
   * @private
304
   */
305
  getFmtBytes_() {
306
    /** @type {!Array<number>} */
307
    let fmtBytes = [];
308
    if (this.fmt.chunkId) {
309
      return fmtBytes.concat(
310
        packString(this.fmt.chunkId),
311
        pack(this.fmt.chunkSize, this.uInt32),
312
        pack(this.fmt.audioFormat, this.uInt16),
313
        pack(this.fmt.numChannels, this.uInt16),
314
        pack(this.fmt.sampleRate, this.uInt32),
315
        pack(this.fmt.byteRate, this.uInt32),
316
        pack(this.fmt.blockAlign, this.uInt16),
317
        pack(this.fmt.bitsPerSample, this.uInt16),
318
        this.getFmtExtensionBytes_());
319
    }
320
    throw Error('Could not find the "fmt " chunk');
321
  }
322
323
  /**
324
   * Return the bytes of the fmt extension fields.
325
   * @return {!Array<number>} The fmt extension bytes.
326
   * @private
327
   */
328
  getFmtExtensionBytes_() {
329
    /** @type {!Array<number>} */
330
    let extension = [];
331
    if (this.fmt.chunkSize > 16) {
332
      extension = extension.concat(
333
        pack(this.fmt.cbSize, this.uInt16));
334
    }
335
    if (this.fmt.chunkSize > 18) {
336
      extension = extension.concat(
337
        pack(this.fmt.validBitsPerSample, this.uInt16));
338
    }
339
    if (this.fmt.chunkSize > 20) {
340
      extension = extension.concat(
341
        pack(this.fmt.dwChannelMask, this.uInt32));
342
    }
343
    if (this.fmt.chunkSize > 24) {
344
      extension = extension.concat(
345
        pack(this.fmt.subformat[0], this.uInt32),
346
        pack(this.fmt.subformat[1], this.uInt32),
347
        pack(this.fmt.subformat[2], this.uInt32),
348
        pack(this.fmt.subformat[3], this.uInt32));
349
    }
350
    return extension;
351
  }
352
353
  /**
354
   * Return the bytes of the 'LIST' chunk.
355
   * @return {!Array<number>} The 'LIST' chunk bytes.
356
   * @private
357
   */
358
  getLISTBytes_() {
359
    /** @type {!Array<number>} */
360
    let bytes = [];
361
    for (let i=0; i<this.LIST.length; i++) {
362
      /** @type {!Array<number>} */
363
      let subChunksBytes = this.getLISTSubChunksBytes_(
364
          this.LIST[i].subChunks, this.LIST[i].format);
365
      bytes = bytes.concat(
366
        packString(this.LIST[i].chunkId),
367
        pack(subChunksBytes.length + 4, this.uInt32),
368
        packString(this.LIST[i].format),
369
        subChunksBytes);
370
    }
371
    return bytes;
372
  }
373
374
  /**
375
   * Return the bytes of the sub chunks of a 'LIST' chunk.
376
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
377
   * @param {string} format The format of the 'LIST' chunk.
378
   *    Currently supported values are 'adtl' or 'INFO'.
379
   * @return {!Array<number>} The sub chunk bytes.
380
   * @private
381
   */
382
  getLISTSubChunksBytes_(subChunks, format) {
383
    /** @type {!Array<number>} */
384
    let bytes = [];
385
    for (let i=0; i<subChunks.length; i++) {
386
      if (format == 'INFO') {
387
        bytes = bytes.concat(
388
          packString(subChunks[i].chunkId),
389
          pack(subChunks[i].value.length + 1, this.uInt32),
390
          writeString(
391
            subChunks[i].value, subChunks[i].value.length));
392
        bytes.push(0);
393
      } else if (format == 'adtl') {
394
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
395
          bytes = bytes.concat(
396
            packString(subChunks[i].chunkId),
397
            pack(
398
              subChunks[i].value.length + 4 + 1, this.uInt32),
399
            pack(subChunks[i].dwName, this.uInt32),
400
            writeString(
401
              subChunks[i].value,
402
              subChunks[i].value.length));
403
          bytes.push(0);
404
        } else if (subChunks[i].chunkId == 'ltxt') {
405
          bytes = bytes.concat(
406
            this.getLtxtChunkBytes_(subChunks[i]));
407
        }
408
      }
409
      if (bytes.length % 2) {
410
        bytes.push(0);
411
      }
412
    }
413
    return bytes;
414
  }
415
416
  /**
417
   * Return the bytes of the '_PMX' chunk.
418
   * @return {!Array<number>} The '_PMX' chunk bytes.
419
   * @private
420
   */
421
  get_PMXBytes_() {
422
    /** @type {!Array<number>} */
423
    let bytes = [];
424
    if (this._PMX.chunkId) {
425
      bytes = bytes.concat(
426
        packString(this._PMX.chunkId),
427
        pack(this._PMX.chunkSize, this.uInt32),
428
        packString(this._PMX.value));
429
    }
430
    return bytes;
431
  }
432
433
  /**
434
   * Return the bytes of a 'ltxt' chunk.
435
   * @param {!Object} ltxt the 'ltxt' chunk.
436
   * @private
437
   */
438
  getLtxtChunkBytes_(ltxt) {
439
    return [].concat(
440
      packString(ltxt.chunkId),
441
      pack(ltxt.value.length + 20, this.uInt32),
442
      pack(ltxt.dwName, this.uInt32),
443
      pack(ltxt.dwSampleLength, this.uInt32),
444
      pack(ltxt.dwPurposeID, this.uInt32),
445
      pack(ltxt.dwCountry, this.uInt16),
446
      pack(ltxt.dwLanguage, this.uInt16),
447
      pack(ltxt.dwDialect, this.uInt16),
448
      pack(ltxt.dwCodePage, this.uInt16),
449
      writeString(ltxt.value, ltxt.value.length));
450
  }
451
452
  /**
453
   * Return the bytes of the 'junk' chunk.
454
   * @private
455
   */
456
  getJunkBytes_() {
457
    /** @type {!Array<number>} */
458
    let bytes = [];
459
    if (this.junk.chunkId) {
460
      return bytes.concat(
461
        packString(this.junk.chunkId),
462
        pack(this.junk.chunkData.length, this.uInt32),
463
        this.junk.chunkData);
464
    }
465
    return bytes;
466
  }
467
}
468